package models

import (
	"context"
	"fmt"
	"github.com/zeromicro/go-zero/core/stores/sqlx"
	"storage/common"
	"strings"
)

var _ StorageModel = (*customStorageModel)(nil)

type (
	StorageModel interface {
		// GetFilesByIDs 根据文件ID列表获取文件列表
		GetFilesByIDs(ctx context.Context, ids []int64) ([]*Files, error)
		// GetFolderByIDs 根据文件夹ID列表获取文件夹列表
		GetFolderByIDs(ctx context.Context, ids []int64) ([]*Folders, error)
		// GetFileList 文件列表: 返回文件数据列表/文件总数/下一次可查询文件ID
		GetFileList(ctx context.Context, userId, parentId, fileType, status, lastId, limit int64, keyword, fileExt, orderBy string) ([]*Files, int64, int64, error)
		// GetFolderList 文件夹列表: 返回文件夹数据列表/文件夹总数/下一次可查询文件夹ID
		GetFolderList(ctx context.Context, userId, parentId, status, lastId, limit int64, keyword, orderBy string) ([]*Folders, int64, int64, error)
		// BatchInsert 批量插入: 批量拷贝文件/文件夹
		BatchInsert(ctx context.Context, userId, parentId, status int64, folderIDs, fileIDs []int64) error
		// BatchUpdate 批量更新: 批量更新文件/文件夹状态
		BatchUpdate(ctx context.Context, userId, parentId, status int64, folderIDs, fileIDs []int64) error
	}

	customStorageModel struct {
		conn        sqlx.SqlConn
		fileModel   defaultFilesModel
		folderModel defaultFoldersModel
	}
)

func NewStorageModel(conn sqlx.SqlConn) StorageModel {
	return &customStorageModel{
		conn: conn,
	}
}

func (m *customStorageModel) GetFilesByIDs(ctx context.Context, ids []int64) ([]*Files, error) {
	var (
		err              error
		files            []*Files
		filePlaceholders []string
		fileArgs         []interface{}
	)
	for _, id := range ids {
		filePlaceholders = append(filePlaceholders, "?")
		fileArgs = append(fileArgs, id)
	}
	query := fmt.Sprintf("select * from %s where id in (%s)", m.fileModel.table, strings.Join(filePlaceholders, ","))
	err = m.conn.QueryRowsCtx(ctx, &files, query, fileArgs...)
	if err != nil {
		return nil, err
	}
	return files, nil
}

func (m *customStorageModel) GetFolderByIDs(ctx context.Context, ids []int64) ([]*Folders, error) {
	var (
		err                error
		folders            []*Folders
		folderPlaceholders []string
		folderArgs         []interface{}
	)
	for _, id := range ids {
		folderPlaceholders = append(folderPlaceholders, "?")
		folderArgs = append(folderArgs, id)
	}
	query := fmt.Sprintf("select * from %s where id in (%s)", m.folderModel.table, strings.Join(folderPlaceholders, ","))
	err = m.conn.QueryRowsCtx(ctx, &folders, query, folderArgs...)
	if err != nil {
		return nil, err
	}
	return folders, nil
}

func (m *customStorageModel) GetFileList(ctx context.Context, userId, parentId, fileType, status, lastId, limit int64,
	keyword, fileExt, orderBy string) ([]*Files, int64, int64, error) {
	countArgs := []interface{}{userId, parentId, status}
	listArgs := []interface{}{userId, parentId, status}
	var count int64
	var files []*Files

	countQuery := fmt.Sprintf("select * from %s where user_id = ? and parent_id = ? and status = ?", m.fileModel.table)
	listQuery := fmt.Sprintf("select * from %s where user_id = ? and parent_id = ? and status = ?", m.fileModel.table)
	if fileType > 0 {
		countQuery += " and file_type = ?"
		countArgs = append(countArgs, fileType)
		listQuery += " and file_type = ?"
		listArgs = append(listArgs, fileType)
	}
	if fileExt != "" {
		countQuery += " and file_ext = ?"
		countArgs = append(countArgs, fileExt)
		listQuery += " and file_ext = ?"
		listArgs = append(listArgs, fileExt)
	}
	if keyword != "" {
		countQuery += " and file_name like ?"
		countArgs = append(countArgs, fmt.Sprintf("%%%s%%", keyword))
		listQuery += " and file_name like ?"
		listArgs = append(listArgs, fmt.Sprintf("%%%s%%", keyword))
	}
	err := m.conn.QueryRowCtx(ctx, &count, countQuery, countArgs...)
	if err != nil {
		return nil, 0, 0, err
	}

	if orderBy == "" {
		orderBy = " id desc"
	}
	if strings.Contains(orderBy, "desc") {
		listQuery += fmt.Sprintf(" and id <= ? %s limit ?", orderBy)
		listArgs = append(listArgs, lastId, limit+1)
	} else if strings.Contains(orderBy, "asc") {
		listQuery += fmt.Sprintf(" and id >= ? %s limit ?", orderBy)
		listArgs = append(listArgs, lastId, limit+1)
	}
	err = m.conn.QueryRowsCtx(ctx, &files, listQuery, listArgs...)
	if err != nil {
		return nil, 0, 0, err
	}

	lastId = files[len(files)-1].Id
	return files, count, lastId, nil
}

func (m *customStorageModel) GetFolderList(ctx context.Context, userId, parentId, status, lastId, limit int64,
	keyword, orderBy string) ([]*Folders, int64, int64, error) {
	countArgs := []interface{}{userId, parentId, status}
	listArgs := []interface{}{userId, parentId, status}
	var count int64
	var folders []*Folders

	countQuery := fmt.Sprintf("select * from %s where user_id = ? and parent_id = ? and status = ?", m.folderModel.table)
	listQuery := fmt.Sprintf("select * from %s where user_id = ? and parent_id = ? and status = ?", m.folderModel.table)
	if keyword != "" {
		countQuery += " and folder_name like ?"
		countArgs = append(countArgs, fmt.Sprintf("%%%s%%", keyword))
		listQuery += " and folders_name like ?"
		listArgs = append(listArgs, fmt.Sprintf("%%%s%%", keyword))
	}
	err := m.conn.QueryRowCtx(ctx, &count, countQuery, countArgs...)
	if err != nil {
		return nil, 0, 0, err
	}

	if orderBy == "" {
		orderBy = " id desc"
	}
	if strings.Contains(orderBy, "desc") {
		listQuery += fmt.Sprintf(" and id <= ? %s limit ?", orderBy)
		listArgs = append(listArgs, lastId, limit+1)
	} else if strings.Contains(orderBy, "asc") {
		listQuery += fmt.Sprintf(" and id >= ? %s limit ?", orderBy)
		listArgs = append(listArgs, lastId, limit)
	}
	err = m.conn.QueryRowsCtx(ctx, &folders, listQuery, listArgs...)
	if err != nil {
		return nil, 0, 0, err
	}
	return folders, count, lastId, nil
}

func (m *customStorageModel) BatchInsert(ctx context.Context, userId, parentId, status int64, folderIDs, fileIDs []int64) error {
	return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
		folders, err := m.VerifyFolders(ctx, session, userId, parentId, status, folderIDs)
		if err != nil {
			return err
		}

		files, err := m.VerifyFiles(ctx, session, userId, parentId, status, fileIDs)
		if err != nil {
			return err
		}

		err = m.InsertFolders(ctx, session, folders)
		if err != nil {
			return err
		}
		err = m.InsertFiles(ctx, session, files)
		if err != nil {
			return err
		}
		return nil
	})
}

func (m *customStorageModel) BatchUpdate(ctx context.Context, userId, parentId, status int64, folderIDs, fileIDs []int64) error {
	return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
		var (
			oldStatus int64
			newStatus int64
		)

		switch status {
		case common.ResourceNormalToRecycle:
			oldStatus = common.ResourceStatusNormal
			newStatus = common.ResourceStatusRecycle
		case common.ResourceRecycleToNormal:
			oldStatus = common.ResourceStatusRecycle
			newStatus = common.ResourceStatusNormal
		case common.ResourceRecycleToDelete:
			oldStatus = common.ResourceStatusRecycle
			newStatus = common.ResourceStatusDelete
		}

		_, err := m.VerifyFolders(ctx, session, userId, parentId, oldStatus, folderIDs)
		if err != nil {
			return err
		}

		_, err = m.VerifyFiles(ctx, session, userId, parentId, oldStatus, fileIDs)
		if err != nil {
			return err
		}

		err = m.UpdateFiles(ctx, session, userId, parentId, newStatus, fileIDs)
		if err != nil {
			return err
		}
		err = m.UpdateFolders(ctx, session, userId, parentId, newStatus, folderIDs)
		if err != nil {
			return err
		}
		return nil
	})
}

func (m *customStorageModel) VerifyFiles(ctx context.Context, session sqlx.Session, userId, parentId, status int64, fileIDs []int64) ([]*Files, error) {
	var (
		err              error
		files            []*Files
		filePlaceholders []string
		fileArgs         []interface{}
	)
	fileQuery := fmt.Sprintf("select * from %s where user_id = ? and status = ? and id in (%s) with for update",
		m.fileModel.table, strings.Join(filePlaceholders, ","))
	err = session.QueryRowsCtx(ctx, &files, fileQuery, userId, status, fileArgs)
	if err != nil {
		return nil, err
	}
	if len(fileIDs) != len(files) {
		return nil, fmt.Errorf("fileIDs not equal fileCount")
	}
	return files, nil
}

func (m *customStorageModel) VerifyFolders(ctx context.Context, session sqlx.Session, userId, parentId, status int64, folderIDs []int64) ([]*Folders, error) {
	var (
		err                error
		folders            []*Folders
		folderPlaceholders []string
		folderArgs         []interface{}
	)
	folderQuery := fmt.Sprintf("select * from %s where user_id = ? and status = ? and id in (%s) with for update",
		m.folderModel.table, strings.Join(folderPlaceholders, ","))
	err = session.QueryRowsCtx(ctx, &folders, folderQuery, userId, status, folderArgs)
	if err != nil {
		return nil, err
	}
	if len(folderIDs) != len(folders) {
		return nil, fmt.Errorf("folderIDs not equal folderCount")
	}
	return folders, nil
}

func (m *customStorageModel) UpdateFiles(ctx context.Context, session sqlx.Session, userId, parentId, status int64, fileIDs []int64) error {
	var (
		err              error
		filePlaceholders []string
		fileArgs         []interface{}
	)
	for _, fileID := range fileIDs {
		filePlaceholders = append(filePlaceholders, "?")
		fileArgs = append(fileArgs, fileID)
	}
	query := fmt.Sprintf("update %s set status = ?, parent_id = ? where user_id = ? and id in (%s)",
		m.fileModel.table, strings.Join(filePlaceholders, ","))
	_, err = session.ExecCtx(ctx, query, status, parentId, userId, fileArgs)
	if err != nil {
		return err
	}
	return nil
}

func (m *customStorageModel) UpdateFolders(ctx context.Context, session sqlx.Session, userId, parentId, status int64, folderIDs []int64) error {
	var (
		err                error
		folderPlaceholders []string
		folderArgs         []interface{}
	)
	for _, folderID := range folderIDs {
		folderPlaceholders = append(folderPlaceholders, "?")
		folderArgs = append(folderArgs, folderID)
	}
	query := fmt.Sprintf("update %s set status = ?, parent_id = ? where user_id = ? and id in (%s)",
		m.folderModel.table, strings.Join(folderPlaceholders, ","))
	_, err = session.ExecCtx(ctx, query, status, parentId, userId, folderArgs)
	if err != nil {
		return err
	}
	return nil
}

func (m *customStorageModel) InsertFiles(ctx context.Context, session sqlx.Session, files []*Files) error {
	var (
		err              error
		filePlaceholders []string
		fileArgs         []interface{}
	)
	for _, file := range files {
		filePlaceholders = append(filePlaceholders, "(?, ?, ?, ?, ?, ?)")
		fileArgs = append(fileArgs, file.UserId, file.ParentId, file.FileName, file.FileSize, file.FileType, file.Status)
	}
	query := fmt.Sprintf("insert into %s (%s) values %s", m.fileModel.table, filesRowsExpectAutoSet, strings.Join(filePlaceholders, ","))
	_, err = session.ExecCtx(ctx, query, fileArgs)
	if err != nil {
		return err
	}
	return nil
}

func (m *customStorageModel) InsertFolders(ctx context.Context, session sqlx.Session, folders []*Folders) error {
	var (
		err              error
		filePlaceholders []string
		fileArgs         []interface{}
	)
	for _, folder := range folders {
		filePlaceholders = append(filePlaceholders, "(?, ?, ?, ?, ?, ?)")
		fileArgs = append(fileArgs, folder.UserId, folder.ParentId, folder.FolderName, folder.Status)
	}
	query := fmt.Sprintf("insert into %s (%s) values %s", m.folderModel.table, foldersRowsExpectAutoSet, strings.Join(filePlaceholders, ","))
	_, err = session.ExecCtx(ctx, query, fileArgs)
	if err != nil {
		return err
	}
	return nil
}
